﻿//
// (C) Copyright 2009 Irantha Suwandarathna (iranthas@hotmail.com)
// All rights reserved.
//

/* Copyright (c) 2001-2008, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use _in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions _in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer _in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using EffiProz.Core.Errors;
using EffiProz.Core.Lib;
using EffiProz.Core.DataTypes;
using EffiProz.Core.RowIO;

namespace EffiProz.Core.Results
{
    public sealed class ResultMetaData
    {
    public const int RESULT_METADATA          = 1;
    public const int SIMPLE_RESULT_METADATA   = 2;
    public const int UPDATE_RESULT_METADATA   = 3;
    public const int PARAM_METADATA           = 4;
    public const int GENERATED_INDEX_METADATA = 5;
    public const int GENERATED_NAME_METADATA  = 6;

    //
    private int _type;

    // values overriding table column
    public String[] columnLabels;
    public SqlType[]   columnTypes;
    private int     columnCount;
    private int     extendedColumnCount;
    public static ResultMetaData emptyResultMetaData =
        newResultMetaData(0);
    public static ResultMetaData emptyParamMetaData =
        newParameterMetaData(0);

    // column indexes for mapping or for generated columns
    public int[] colIndexes;

    // columns for data columns
    public ColumnBase[] columns;

    // param mode and nullability for parameter metadata
    public byte[] paramModes;
    public byte[] paramNullable;

    //
    private ResultMetaData(int type) {
        this._type = type;
    }

    public static ResultMetaData newUpdateResultMetaData(SqlType[] types) {

        ResultMetaData md = new ResultMetaData(UPDATE_RESULT_METADATA);

        md.columnTypes         = new SqlType[types.Length];
        md.columnCount         = types.Length;
        md.extendedColumnCount = types.Length;

        Array.Copy(types, md.columnTypes, types.Length);

        return md;
    }

    public static ResultMetaData newSimpleResultMetaData(SqlType[] types) {

        ResultMetaData md = new ResultMetaData(SIMPLE_RESULT_METADATA);

        md.columnTypes         = types;
        md.columnCount         = types.Length;
        md.extendedColumnCount = types.Length;

        return md;
    }

    public static ResultMetaData newResultMetaData(int colCount) {

        SqlType[] types = new SqlType[colCount];

        return newResultMetaData(types, null, colCount, colCount);
    }

    public static ResultMetaData newResultMetaData(SqlType[] types,
            int[] baseColumnIndexes, int colCount, int extColCount) {

        ResultMetaData md = new ResultMetaData(RESULT_METADATA);

        md.columnLabels        = new String[colCount];
        md.columns             = new ColumnBase[colCount];
        md.columnTypes         = types;
        md.colIndexes          = baseColumnIndexes;
        md.columnCount         = colCount;
        md.extendedColumnCount = extColCount;

        return md;
    }

    public static ResultMetaData newParameterMetaData(int colCount) {

        ResultMetaData md = new ResultMetaData(PARAM_METADATA);

        md.columnTypes         = new SqlType[colCount];
        md.columnLabels        = new String[colCount];
        md.paramModes          = new byte[colCount];
        md.paramNullable       = new byte[colCount];
        md.columnCount         = colCount;
        md.extendedColumnCount = colCount;

        return md;
    }

    public static ResultMetaData newGeneratedColumnsMetaData(
            int[] columnIndexes, String[] columnNames) {

        if (columnIndexes != null) {
            ResultMetaData md = new ResultMetaData(GENERATED_INDEX_METADATA);

            md.columnCount         = columnIndexes.Length;
            md.extendedColumnCount = columnIndexes.Length;
            md.colIndexes          = new int[columnIndexes.Length];

            for (int i = 0; i < columnIndexes.Length; i++) {
                md.colIndexes[i] = columnIndexes[i] - 1;
            }

            return md;
        } else if (columnNames != null) {
            ResultMetaData md = new ResultMetaData(GENERATED_NAME_METADATA);

            md.columnLabels        = new String[columnNames.Length];
            md.columnCount         = columnNames.Length;
            md.extendedColumnCount = columnNames.Length;
            md.columnLabels        = columnNames;

            return md;
        } else {
            return null;
        }
    }

    public void prepareData() {

        if (columns != null) {
            for (int i = 0; i < columnCount; i++) {
                if (columnTypes[i] == null) {
                    columnTypes[i] = columns[i].getDataType();
                }
            }
        }
    }

    public int getColumnCount() {
        return columnCount;
    }

    public int getExtendedColumnCount() {
        return extendedColumnCount;
    }

    public void resetExtendedColumnCount() {
        extendedColumnCount = columnCount;
    }

    public SqlType[] getParameterTypes() {
        return columnTypes;
    }

    public String[] getGeneratedColumnNames() {
        return columnLabels;
    }

    public int[] getGeneratedColumnIndexes() {
        return colIndexes;
    }

    public bool isTableColumn(int i) {

        String colName   = columns[i].getNameString();
        String tableName = columns[i].getTableNameString();

        return tableName != null && tableName.Length > 0 && colName != null
               && colName.Length > 0;
    }

    private static void decodeTableColumnAttrs(int _in, ColumnBase column) {
        column.setNullability((byte) (_in & 0x00000003));
        column.setIdentity((_in & 0x00000004) != 0);
        column.setWriteable((_in & 0x00000008) != 0);
    }

    private static int encodeTableColumnAttrs(ColumnBase column) {

        int _out = column.getNullability();    // always between 0x00 and 0x02

        if (column.isIdentity()) {
            _out |= 0x00000004;
        }

        if (column.isWriteable()) {
            _out |= 0x00000008;
        }

        return _out;
    }

    private void decodeParamColumnAttrs(int _in, int columnIndex) {
        paramNullable[columnIndex] = (byte) (_in & 0x00000003);
        paramModes[columnIndex]    = (byte) ((_in >> 4) & 0x0000000f);
    }

    private int encodeParamColumnAttrs(int columnIndex) {

        int _out = paramModes[columnIndex] << 4;

        _out |= paramNullable[columnIndex];

        return _out;
    }

    public ResultMetaData(RowInputBinary _in)  {

        _type        = _in.readInt();
        columnCount = _in.readInt();

        switch (_type) {

            case UPDATE_RESULT_METADATA :
            case SIMPLE_RESULT_METADATA : {
                columnTypes = new SqlType[columnCount];

                for (int i = 0; i < columnCount; i++) {
                    int type = _in.readType();

                    columnTypes[i] = SqlType.getDefaultType(type);
                }

                return;
            }
            case GENERATED_INDEX_METADATA : {
                colIndexes = new int[columnCount];

                for (int i = 0; i < columnCount; i++) {
                    colIndexes[i] = _in.readInt();
                }

                return;
            }
            case GENERATED_NAME_METADATA : {
                columnLabels = new String[columnCount];

                for (int i = 0; i < columnCount; i++) {
                    columnLabels[i] = _in.readString();
                }

                return;
            }
            case PARAM_METADATA : {
                columnTypes   = new SqlType[columnCount];
                columnLabels  = new String[columnCount];
                paramModes    = new byte[columnCount];
                paramNullable = new byte[columnCount];

                for (int i = 0; i < columnCount; i++) {
                    columnTypes[i]  = readDataType(_in);
                    columnLabels[i] = _in.readString();

                    decodeParamColumnAttrs(_in.readByte(), i);
                }

                return;
            }
            case RESULT_METADATA : {
                extendedColumnCount = _in.readInt();
                columnTypes         = new SqlType[extendedColumnCount];
                columnLabels        = new String[columnCount];
                columns             = new ColumnBase[columnCount];

                if (columnCount != extendedColumnCount) {
                    colIndexes = new int[columnCount];
                }

                for (int i = 0; i < extendedColumnCount; i++) {
                    SqlType type = readDataType(_in);

                    columnTypes[i] = type;
                }

                for (int i = 0; i < columnCount; i++) {
                    columnLabels[i] = _in.readString();

                    String catalog = _in.readString();
                    String schema  = _in.readString();
                    String table   = _in.readString();
                    String name    = _in.readString();
                    ColumnBase column = new ColumnBase(catalog, schema, table,
                                                       name);

                    column.setType(columnTypes[i]);
                    decodeTableColumnAttrs(_in.readByte(), column);

                    columns[i] = column;
                }

                if (columnCount != extendedColumnCount) {
                    for (int i = 0; i < columnCount; i++) {
                        colIndexes[i] = _in.readInt();
                    }
                }

                return;
            }
            default : {
                throw Error.runtimeError(ErrorCode.U_S0500, "ResultMetaData");
            }
        }
    }

    public SqlType readDataType(RowInputBinary _in)  {

        int  typeCode = _in.readType();
        long size     = _in.readLong();
        int  scale    = _in.readInt();

        return SqlType.getType(typeCode, 0, size, scale);
    }

    public void writeDataType(RowOutputInterface _out, SqlType type)
    {

       _out.writeType(type.typeCode);
       _out.writeLong(type.precision);
       _out.writeInt(type.scale);
    }

    public void write(RowOutputInterface _out)  {

       _out.writeInt(_type);
       _out.writeInt(columnCount);

        switch (_type) {

            case UPDATE_RESULT_METADATA :
            case SIMPLE_RESULT_METADATA : {
                for (int i = 0; i < columnCount; i++) {
                   _out.writeType(columnTypes[i].typeCode);
                }

                return;
            }
            case GENERATED_INDEX_METADATA : {
                for (int i = 0; i < columnCount; i++) {
                   _out.writeInt(colIndexes[i]);
                }

                return;
            }
            case GENERATED_NAME_METADATA : {
                for (int i = 0; i < columnCount; i++) {
                   _out.writeString(columnLabels[i]);
                }

                return;
            }
            case PARAM_METADATA :
                for (int i = 0; i < columnCount; i++) {
                    writeDataType(_out, columnTypes[i]);
                   _out.writeString(columnLabels[i]);
                   _out.writeByte((byte)encodeParamColumnAttrs(i));
                }

                return;

            case RESULT_METADATA : {
               _out.writeInt(extendedColumnCount);

                for (int i = 0; i < extendedColumnCount; i++) {
                    if (columnTypes[i] == null) {
                        ColumnBase column = columns[i];

                        columnTypes[i] = column.getDataType();
                    }

                    writeDataType(_out, columnTypes[i]);
                }

                for (int i = 0; i < columnCount; i++) {
                    ColumnBase column = columns[i];

                   _out.writeString(columnLabels[i]);
                   _out.writeString(column.getCatalogNameString());
                   _out.writeString(column.getSchemaNameString());
                   _out.writeString(column.getTableNameString());
                   _out.writeString(column.getNameString());
                   _out.writeByte((byte)encodeTableColumnAttrs(column));
                }

                if (columnCount != extendedColumnCount) {
                    for (int i = 0; i < colIndexes.Length; i++) {
                       _out.writeInt(colIndexes[i]);
                    }
                }

                return;
            }
            default : {
                throw Error.runtimeError(ErrorCode.U_S0500, "ResultMetaData");
            }
        }
    }

    public ResultMetaData getNewMetaData(int[] columnMap) {

        ResultMetaData newMeta = newResultMetaData(columnMap.Length);

        ArrayUtil.projectRow(columnLabels, columnMap, newMeta.columnLabels);
        ArrayUtil.projectRow(columnTypes, columnMap, newMeta.columnTypes);
        ArrayUtil.projectRow(columns, columnMap, newMeta.columns);

        return newMeta;
    }
    }
}
